﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Threading;

using agsXMPP.protocol.client;
using agsXMPP.protocol.x.vcard_update;

using VSTalk.Engine.Core.EntitySession;
using VSTalk.Engine.Core.XmppConnection.PresenceListener;
using VSTalk.Engine.Core.XmppUtils;
using VSTalk.Model;
using VSTalk.Tools;

namespace VSTalk.Engine.Core.ContactInfo
{
    public class ContactInfoSevice : IContactInfoSevice, IDisposable, IListener
    {
        private IContactInfoDataProvider _dataProvider = new ContactInfoDataProvider();
        private MultiClientPresenceListener _presenceListener = new MultiClientPresenceListener();
        private IActionWrapper<Client, Presence> _onPresence;
        private IDictionary<Tuple<string, string>, ContactInfoRequest> _pendingReuests = new Dictionary<Tuple<string, string>, ContactInfoRequest>();
        private Dispatcher _dispatcher;
        private IDictionary<string, ConactDetailsProvider> _providers = new Dictionary<string, ConactDetailsProvider>();

        private static ISessionModel SessionModel
        {
            get { return AppServices.Get<ISessionModel>(); }
        }

        public ContactInfoSevice()
        {
            StartListen();
        }

        public void StartListen()
        {
            _dispatcher = Dispatcher.CurrentDispatcher;
            _presenceListener.StartListen();
            _onPresence = _dispatcher.Wrap<Client, Presence>(OnPresence);
            _presenceListener.PresenceReceived += _onPresence.Exec;
        }

        public void StopListen()
        {
            _presenceListener.PresenceReceived -= _onPresence.Exec;   
            _presenceListener.StopListen();
        }

        private void OnPresence(Client client, Presence presence)
        {
            var clientContext = SessionModel.GetClientContext(client);
            if (clientContext.Jid.Equals(presence.From, new CaseInsensitiveComparer()))
            {
                return;
            }
            var vcardUpdate = presence.SelectSingleElement<VcardUpdate>();
            if (vcardUpdate == null)
            {
                return;
            }
            if (string.IsNullOrEmpty(vcardUpdate.Photo))
            {
                return;
            }
            var bare = presence.From.Bare.ToLower();
            var hash = vcardUpdate.Photo;
            var key = Tuple.Create(bare, hash);
            if (_pendingReuests.ContainsKey(key))
            {
                return;
            }
            if (_dataProvider.HasPhoto(bare, hash))
            {
                return;
            }
            var jid = presence.From.ToString().ToLower();
            InitiateRequest(client, key, jid);
        }

        private void InitiateRequest(Client client, Tuple<string, string> key, string jid)
        {
            var request = new ContactInfoRequest(client, jid);
            _pendingReuests.Add(key, request);
            request.Send().ContinueWith(_dispatcher.Wrap<Task<ContactDetails>>(task =>
            {
                _pendingReuests.Remove(key);
                if (task.Result == null)
                {
                    return;
                }
                _dataProvider.RecordDetails(key.Item1, key.Item2, task.Result);
                if (_providers.ContainsKey(key.Item1))
                {
                    _providers[key.Item1].Set(task.Result);
                }
            }).Exec);
        }

        public void Subscribe(Interlocutor interlocutor, IContactDetailsMonitor monitor)
        {
            var jid = XmppIdConverter.Jid(interlocutor);
            if (_providers.ContainsKey(jid))
            {
                _providers[jid].Add(monitor);
                return;
            }
            var latest = _dataProvider.GetLatestInfo(jid);
            var provider = new ConactDetailsProvider(jid, latest);
            _providers[jid] = provider;
            provider.Add(monitor);
        }

        public void Unsubscribe(Interlocutor interlocutor, IContactDetailsMonitor monitor)
        {
            var jid = XmppIdConverter.Jid(interlocutor);
            if (!_providers.ContainsKey(jid))
            {
                return;
            }
            var provider = _providers[jid];
            provider.Remove(monitor);
            if (provider.IsEmpty())
            {
                _providers.Remove(jid);
            }
        }

        public void Dispose()
        {
            StopListen();
        }
    }
}